module alu(
  input  [19:0] alu_op,
  input  [31:0] alu_src1,
  input  [31:0] alu_src2,
  input  clk,
  input  reset,
  input  alu_div,
  input  alu_divu,
  input  flush,
  output [31:0] alu_result,
  output alu_overflow,
  output div_finish
);

wire op_add;   //�ӷ�����
wire op_sub;   //��������
wire op_slt;   //�з��űȽϣ�С����λ
wire op_sltu;  //�޷��űȽϣ�С����λ
wire op_and;   //��λ��
wire op_nor;   //��λ���?
wire op_or;    //��λ��
wire op_xor;   //��λ���?
wire op_sll;   //�߼�����
wire op_srl;   //�߼�����
wire op_sra;   //��������
wire op_lui;   //���������ڸ߰벿��
wire op_mult;
wire op_multu;
wire op_div;
wire op_divu;
wire op_mfhi;
wire op_mflo;
wire op_mthi;
wire op_mtlo;

//�˳������ӼĴ���
reg  [31:0] hi;
reg  [31:0] lo;
wire [31:0] hi_result;
wire [31:0] lo_result;

// control code decomposition
assign op_add  = alu_op[ 0];
assign op_sub  = alu_op[ 1];
assign op_slt  = alu_op[ 2];
assign op_sltu = alu_op[ 3];
assign op_and  = alu_op[ 4];
assign op_nor  = alu_op[ 5];
assign op_or   = alu_op[ 6];
assign op_xor  = alu_op[ 7];
assign op_sll  = alu_op[ 8];
assign op_srl  = alu_op[ 9];
assign op_sra  = alu_op[10];
assign op_lui  = alu_op[11];
assign op_mult = alu_op[12];
assign op_multu= alu_op[13];
assign op_div  = alu_op[14];
assign op_divu = alu_op[15];
assign op_mfhi = alu_op[16];
assign op_mflo = alu_op[17];
assign op_mthi = alu_op[18];
assign op_mtlo = alu_op[19];

wire [31:0] add_sub_result; 
wire [31:0] slt_result; 
wire [31:0] sltu_result;
wire [31:0] and_result;
wire [31:0] nor_result;
wire [31:0] or_result;
wire [31:0] xor_result;
wire [31:0] lui_result;
wire [31:0] sll_result; 
wire [63:0] sr64_result; 
wire [31:0] sr_result;
wire [63:0] mult_result; 
wire [63:0] multu_result;
wire [63:0] div_result;
wire [63:0] divu_result;

wire div_dend_ready;
reg  div_dend_valid;
wire div_sor_ready;
reg  div_sor_valid;
wire div_out_valid;
wire divu_dend_ready;
reg  divu_dend_valid;
wire divu_sor_ready;
reg  divu_sor_valid;
wire divu_out_valid;

// 32-bit adder
wire [32:0] adder_a;
wire [32:0] adder_b;
wire        adder_cin;
wire [32:0] adder_result;
wire        adder_cout;

assign adder_a   = {alu_src1[31], alu_src1};
assign adder_b   = {32{op_sub | op_slt | op_sltu}} & {~alu_src2[31], ~alu_src2} 
                 | {32{op_add}} & {alu_src2[31], alu_src2};
assign adder_cin = op_sub | op_slt | op_sltu;
assign {adder_cout, adder_result} = adder_a + adder_b + adder_cin;

// ADD, SUB result
assign add_sub_result = adder_result;

// SLT result
assign slt_result[31:1] = 31'b0;
assign slt_result[0]    = (alu_src1[31] & ~alu_src2[31])
                        | ((alu_src1[31] ~^ alu_src2[31]) & adder_result[31]);

// SLTU result
assign sltu_result[31:1] = 31'b0;
assign sltu_result[0]    = ~adder_cout;

// bitwise operation
assign and_result = alu_src1 & alu_src2;
assign or_result  = alu_src1 | alu_src2;
assign nor_result = ~or_result;
assign xor_result = alu_src1 ^ alu_src2;
assign lui_result = {alu_src2[15:0], 16'b0};

// SLL result 
assign sll_result = alu_src2 << alu_src1[4:0];

// SRL, SRA result
assign sr64_result = {{32{op_sra & alu_src2[31]}}, alu_src2[31:0]} >> alu_src1[4:0];

assign sr_result   = sr64_result[31:0];

// MULT, MULTU result
assign mult_result = $signed(alu_src1) * $signed(alu_src2);
assign multu_result = alu_src1 * alu_src2;

//DIV, DIVU result
mydiv mydiv(
    .s_axis_dividend_tdata(alu_src1),
    .s_axis_dividend_tready(div_dend_ready),
    .s_axis_dividend_tvalid(div_dend_valid),
    
    .s_axis_divisor_tdata(alu_src2),
    .s_axis_divisor_tready(div_sor_ready),
    .s_axis_divisor_tvalid(div_sor_valid),
    
    .m_axis_dout_tdata(div_result),
    .m_axis_dout_tvalid(div_out_valid),
    
    .aclk(clk)
);
always @(posedge clk) begin
    if (reset) begin
        div_dend_valid <= 1'b0;
    end
    else if (alu_div) begin
        div_dend_valid <= 1'b1;
    end
    else if ((div_dend_ready == 1'b1) && (div_dend_valid == 1'b1)) begin
        div_dend_valid <= 1'b0;
    end
    
    if (reset) begin
        div_sor_valid <= 1'b0;
    end
    else if (alu_div) begin
        div_sor_valid <= 1'b1;
    end
    else if ((div_sor_ready == 1'b1) && (div_sor_valid == 1'b1)) begin
        div_sor_valid <= 1'b0;
    end
end
mydivu mydivu(
    .s_axis_dividend_tdata(alu_src1),
    .s_axis_dividend_tready(divu_dend_ready),
    .s_axis_dividend_tvalid(divu_dend_valid),
    
    .s_axis_divisor_tdata(alu_src2),
    .s_axis_divisor_tready(divu_sor_ready),
    .s_axis_divisor_tvalid(divu_sor_valid),
    
    .m_axis_dout_tdata(divu_result),
    .m_axis_dout_tvalid(divu_out_valid),
    
    .aclk(clk)
);
always @(posedge clk) begin
    if (reset) begin
        divu_dend_valid <= 1'b0;
    end
    else if (alu_divu) begin
        divu_dend_valid <= 1'b1;
    end
    else if ((divu_dend_ready == 1'b1) && (divu_dend_valid == 1'b1)) begin
        divu_dend_valid <= 1'b0;
    end
    
    if (reset) begin
        divu_sor_valid <= 1'b0;
    end
    else if (alu_divu) begin
        divu_sor_valid <= 1'b1;
    end
    else if ((divu_sor_ready == 1'b1) && (divu_sor_valid == 1'b1)) begin
        divu_sor_valid <= 1'b0;
    end
end

//HI, LO
assign hi_we = ~flush & (op_mult | op_multu | (op_div & div_out_valid) | (op_divu & divu_out_valid) | op_mthi);
assign lo_we = ~flush & (op_mult | op_multu | (op_div & div_out_valid) | (op_divu & divu_out_valid) | op_mtlo);
assign hi_result = {32{op_mult }} & mult_result[63:32]
                 | {32{op_multu}} & multu_result[63:32]
                 | {32{op_div  }} & div_result[31:0]
                 | {32{op_divu }} & divu_result[31:0]
                 | {32{op_mthi }} & alu_src1;
assign lo_result = {32{op_mult }} & mult_result[31:0]
                 | {32{op_multu}} & multu_result[31:0]
                 | {32{op_div  }} & div_result[63:32]
                 | {32{op_divu }} & divu_result[63:32]
                 | {32{op_mtlo }} & alu_src1;
always @(posedge clk) begin
    if (hi_we) begin
        hi <= hi_result;
    end
    
    if (lo_we) begin
        lo <= lo_result;
    end
end


// final result mux
assign alu_result = ({32{op_add|op_sub}} & add_sub_result)
                  | ({32{op_slt       }} & slt_result)
                  | ({32{op_sltu      }} & sltu_result)
                  | ({32{op_and       }} & and_result)
                  | ({32{op_nor       }} & nor_result)
                  | ({32{op_or        }} & or_result)
                  | ({32{op_xor       }} & xor_result)
                  | ({32{op_lui       }} & lui_result)
                  | ({32{op_sll       }} & sll_result)
                  | ({32{op_srl|op_sra}} & sr_result)
                  | ({32{op_mfhi      }} & hi)
                  | ({32{op_mflo      }} & lo);
                  
assign div_finish = (op_div & div_out_valid) | (op_divu & divu_out_valid);

assign alu_overflow = !(adder_result[32] == adder_result[31]);

endmodule
